/* Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */

#include <my_global.h>
#include <my_sys.h>
#include "my_default.h"
#include <mysql.h>
#include <errmsg.h>
#include <my_getopt.h>
#include <m_string.h>
#include <mysqld_error.h>
#include <sql_common.h>
#include <mysql/client_plugin.h>

#define VER "2.1"
#define MAX_TEST_QUERY_LENGTH 300 /* MAX QUERY BUFFER LENGTH */
#define MAX_KEY MAX_INDEXES
#define MAX_SERVER_ARGS 64

/* set default options */
static int   opt_testcase = 0;
static char *opt_db= 0;
static char *opt_user= 0;
static char *opt_password= 0;
static char *opt_host= 0;
static char *opt_unix_socket= 0;
#if defined (_WIN32) && !defined (EMBEDDED_LIBRARY)
static char *shared_memory_base_name= 0;
#endif
static unsigned int  opt_port;
static my_bool tty_password= 0, opt_silent= 0;

static my_bool opt_secure_auth= 1;
static MYSQL *mysql= 0;
static char current_db[]= "client_test_db";
static unsigned int test_count= 0;
static unsigned int opt_count= 0;
static unsigned int opt_count_read= 0;
static unsigned int iter_count= 0;
static my_bool have_innodb= FALSE;
static char *opt_plugin_dir= 0, *opt_default_auth= 0;
static unsigned int opt_drop_db= 1;

static const char *opt_basedir= "./";
static const char *opt_vardir= "mysql-test/var";

static longlong opt_getopt_ll_test= 0;

static char **defaults_argv;
static int   original_argc;
static char **original_argv;
static int embedded_server_arg_count= 0;
static char *embedded_server_args[MAX_SERVER_ARGS];

static const char *embedded_server_groups[]= {
"server",
"embedded",
"mysql_client_test_SERVER",
NullS
};

static time_t start_time, end_time;
static double total_time;

const char *default_dbug_option= "d:t:o,/tmp/mysql_client_test.trace";

/*
Read and parse arguments and MySQL options from my.cnf
*/
static const char *client_test_load_default_groups[]= { "client", 0 };

struct my_tests_st
{
const char *name;
void       (*function)();
int        version_min;
int        version_max;
};

#define myheader(str)							\
DBUG_PRINT("test", ("name: %s", str));					\
 if (opt_silent < 2)							\
 {									\
   fprintf(stdout, "\n\n#####################################\n");	\
   fprintf(stdout, "%u of (%u/%u): %s", test_count++, iter_count,	\
   opt_count, str);							\
   fprintf(stdout, "  \n#####################################\n");	\
 }

#define myheader_r(str)							\
DBUG_PRINT("test", ("name: %s", str));					\
 if (!opt_silent)							\
 {									\
   fprintf(stdout, "\n\n#####################################\n");	\
   fprintf(stdout, "%s", str);						\
   fprintf(stdout, "  \n#####################################\n");	\
 }

static void print_error(MYSQL * l_mysql, const char *msg);
static void print_st_error(MYSQL_STMT *stmt, const char *msg);
static void client_disconnect(MYSQL* mysql);
static void get_options(int *argc, char ***argv);

/*
Abort unless given experssion is non-zero.

SYNOPSIS
DIE_UNLESS(expr)

DESCRIPTION
We can't use any kind of system assert as we need to
preserve tested invariants in release builds as well.
*/

#define DIE_UNLESS(expr)					\
((void) ((expr) ? 0 : (die(__FILE__, __LINE__, #expr), 0)))
#define DIE_IF(expr)						\
((void) ((expr) ? (die(__FILE__, __LINE__, #expr), 0) : 0))
#define DIE(expr)				\
die(__FILE__, __LINE__, #expr)

static void die(const char *file, int line, const char *expr)
{
 fflush(stdout);
 fprintf(stderr, "%s:%d: check failed: '%s'\n", file, line, expr);
 fflush(stderr);
 exit(1);
}


#define myerror(msg) print_error(mysql,msg)
#define myerror2(l_mysql, msg) print_error(l_mysql,msg)
#define mysterror(stmt, msg) print_st_error(stmt, msg)

#define myquery(RES)				\
{						\
 int r= (RES);					\
 if (r)						\
 myerror(NULL);					\
 DIE_UNLESS(r == 0);				\
}

#define myquery2(L_MYSQL,RES)			\
{						\
 int r= (RES);					\
 if (r)						\
 myerror2(L_MYSQL,NULL);			\
 DIE_UNLESS(r == 0);				\
}

#define myquery_r(r)				\
{						\
 if (r)						\
 myerror(NULL);					\
 DIE_UNLESS(r != 0);				\
}

#define check_execute(stmt, r)			\
{						\
 if (r)						\
 mysterror(stmt, NULL);				\
 DIE_UNLESS(r == 0);				\
}

#define check_execute_r(stmt, r)		\
{						\
 if (r)						\
 mysterror(stmt, NULL);				\
 DIE_UNLESS(r != 0);				\
}

#define check_stmt(stmt)			\
{						\
 if ( stmt == 0)				\
 myerror(NULL);					\
 DIE_UNLESS(stmt != 0);				\
}

#define check_stmt_r(stmt)			\
{						\
 if (stmt == 0)					\
 myerror(NULL);					\
 DIE_UNLESS(stmt == 0);				\
}

#define mytest(x) if (!(x)) {myerror(NULL);DIE_UNLESS(FALSE);}
#define mytest_r(x) if ((x)) {myerror(NULL);DIE_UNLESS(FALSE);}

/* Silence unused function warnings for some of the static functions. */
static int cmp_double(double *a, double *b) MY_ATTRIBUTE((unused));
static void verify_col_data(const char *table, const char *col,
                            const char *exp_data) MY_ATTRIBUTE((unused));
static void do_verify_prepare_field(MYSQL_RES *result, unsigned int no,
                                    const char *name, const char *org_name,
                                    enum enum_field_types type,
                                    const char *table, const char *org_table,
                                    const char *db, unsigned long length,
                                    const char *def, const char *file,
                                    int line) MY_ATTRIBUTE((unused));
static void verify_st_affected_rows(MYSQL_STMT *stmt,
                                    ulonglong exp_count) MY_ATTRIBUTE((unused));
static void verify_affected_rows(ulonglong exp_count) MY_ATTRIBUTE((unused));
static void verify_field_count(MYSQL_RES *result,
                               uint exp_count) MY_ATTRIBUTE((unused));
#ifndef EMBEDDED_LIBRARY
static void execute_prepare_query(const char *query,
                                  ulonglong exp_count) MY_ATTRIBUTE((unused));
#endif
static my_bool thread_query(const char *query) MY_ATTRIBUTE((unused));


/* A workaround for Sun Forte 5.6 on Solaris x86 */

static int cmp_double(double *a, double *b)
{
 return *a == *b;
}


/* Print the error message */

static void print_error(MYSQL *l_mysql, const char *msg)
{
 if (!opt_silent)
 {
   if (l_mysql && mysql_errno(l_mysql))
   {
     if (l_mysql->server_version)
     fprintf(stdout, "\n [MySQL-%s]", l_mysql->server_version);
     else
     fprintf(stdout, "\n [MySQL]");
     fprintf(stdout, "[%d] %s\n", mysql_errno(l_mysql), mysql_error(l_mysql));
   }
   else if (msg)
   fprintf(stderr, " [MySQL] %s\n", msg);
 }
}


static void print_st_error(MYSQL_STMT *stmt, const char *msg)
{
 if (!opt_silent)
 {
   if (stmt && mysql_stmt_errno(stmt))
   {
     if (stmt->mysql && stmt->mysql->server_version)
     fprintf(stdout, "\n [MySQL-%s]", stmt->mysql->server_version);
     else
     fprintf(stdout, "\n [MySQL]");

     fprintf(stdout, "[%d] %s\n", mysql_stmt_errno(stmt),
     mysql_stmt_error(stmt));
   }
   else if (msg)
   fprintf(stderr, " [MySQL] %s\n", msg);
 }
}

/*
Enhanced version of mysql_client_init(), which may also set shared memory
base on Windows.
*/
static MYSQL *mysql_client_init(MYSQL* con)
{
 MYSQL* res = mysql_init(con);
 #if defined (_WIN32) && !defined (EMBEDDED_LIBRARY)
 if (res && shared_memory_base_name)
 mysql_options(res, MYSQL_SHARED_MEMORY_BASE_NAME, shared_memory_base_name);
 #endif
 if (opt_plugin_dir && *opt_plugin_dir)
 mysql_options(res, MYSQL_PLUGIN_DIR, opt_plugin_dir);

 if (opt_default_auth && *opt_default_auth)
 mysql_options(res, MYSQL_DEFAULT_AUTH, opt_default_auth);

 if (!opt_secure_auth)
 mysql_options(res, MYSQL_SECURE_AUTH, (char*)&opt_secure_auth);
 return res;
}

/*
Disable direct calls of mysql_init, as it disregards  shared memory base.
*/
#define mysql_init(A) Please use mysql_client_init instead of mysql_init


/* Check if the connection has InnoDB tables */

static my_bool check_have_innodb(MYSQL *conn)
{
 MYSQL_RES *res;
 MYSQL_ROW row;
 int rc;
 my_bool result= FALSE;

 rc= mysql_query(conn,
 "SELECT (support = 'YES' or support = 'DEFAULT' or support = 'ENABLED') "
 "AS `TRUE` FROM information_schema.engines WHERE engine = 'innodb'");
 myquery(rc);
 res= mysql_use_result(conn);
 DIE_UNLESS(res);

 row= mysql_fetch_row(res);
 DIE_UNLESS(row);

 if (row[0] && row[1])
 result= strcmp(row[1], "1") == 0;
 mysql_free_result(res);
 return result;
}


/*
This is to be what mysql_query() is for mysql_real_query(), for
mysql_simple_prepare(): a variant without the 'length' parameter.
*/

static MYSQL_STMT *STDCALL
mysql_simple_prepare(MYSQL *mysql_arg, const char *query)
{
 MYSQL_STMT *stmt= mysql_stmt_init(mysql_arg);
 if (stmt && mysql_stmt_prepare(stmt, query, (ulong)strlen(query)))
 {
   mysql_stmt_close(stmt);
   return 0;
 }
 return stmt;
}


/**
Connect to the server with options given by arguments to this application,
stored in global variables opt_host, opt_user, opt_password, opt_db,
opt_port and opt_unix_socket.

@param flag[in]           client_flag passed on to mysql_real_connect
@param protocol[in]       MYSQL_PROTOCOL_* to use for this connection
@param auto_reconnect[in] set to 1 for auto reconnect

@return pointer to initialized and connected MYSQL object
*/
static MYSQL* client_connect(ulong flag, uint protocol, my_bool auto_reconnect)
{
 MYSQL* mysql;
 int  rc;
 static char query[MAX_TEST_QUERY_LENGTH];
 myheader_r("client_connect");

 if (!opt_silent)
 fprintf(stdout, "\n Establishing a connection to '%s' ...",
 opt_host ? opt_host : "");

 if (!(mysql= mysql_client_init(NULL)))
 {
   opt_silent= 0;
   myerror("mysql_client_init() failed");
   exit(1);
 }
 /* enable local infile, in non-binary builds often disabled by default */
 mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0);
 mysql_options(mysql, MYSQL_OPT_PROTOCOL, &protocol);
 if (opt_plugin_dir && *opt_plugin_dir)
 mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir);

 if (opt_default_auth && *opt_default_auth)
 mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth);

 if (!opt_secure_auth)
 mysql_options(mysql, MYSQL_SECURE_AUTH, (char*)&opt_secure_auth);

 if (!(mysql_real_connect(mysql, opt_host, opt_user,
 opt_password, opt_db ? opt_db:"test", opt_port,
 opt_unix_socket, flag)))
 {
   opt_silent= 0;
   myerror("connection failed");
   mysql_close(mysql);
   fprintf(stdout, "\n Check the connection options using --help or -?\n");
   exit(1);
 }
 mysql->reconnect= auto_reconnect;

 if (!opt_silent)
 fprintf(stdout, "OK");

 /* set AUTOCOMMIT to ON*/
 mysql_autocommit(mysql, TRUE);

 if (!opt_silent)
 {
   fprintf(stdout, "\nConnected to MySQL server version: %s (%lu)\n",
   mysql_get_server_info(mysql),
   (ulong) mysql_get_server_version(mysql));
   fprintf(stdout, "\n Creating a test database '%s' ...", current_db);
 }
 strxmov(query, "CREATE DATABASE IF NOT EXISTS ", current_db, NullS);

 rc= mysql_query(mysql, query);
 myquery(rc);

 strxmov(query, "USE ", current_db, NullS);
 rc= mysql_query(mysql, query);
 myquery(rc);
 have_innodb= check_have_innodb(mysql);

 if (!opt_silent)
 fprintf(stdout, "OK");

 return mysql;
}


/* Close the connection */

static void client_disconnect(MYSQL* mysql)
{
 static char query[MAX_TEST_QUERY_LENGTH];

 myheader_r("client_disconnect");

 if (mysql)
 {
   if (opt_drop_db)
   {
     if (!opt_silent)
     fprintf(stdout, "\n dropping the test database '%s' ...", current_db);
     strxmov(query, "DROP DATABASE IF EXISTS ", current_db, NullS);

     mysql_query(mysql, query);
     if (!opt_silent)
     fprintf(stdout, "OK");
   }

   if (!opt_silent)
   fprintf(stdout, "\n closing the connection ...");
   mysql_close(mysql);
   if (!opt_silent)
   fprintf(stdout, "OK\n");
 }
}


/* Print dashes */

static void my_print_dashes(MYSQL_RES *result)
{
 MYSQL_FIELD  *field;
 unsigned int i, j;

 mysql_field_seek(result, 0);
 fputc('\t', stdout);
 fputc('+', stdout);

 for(i= 0; i< mysql_num_fields(result); i++)
 {
   field= mysql_fetch_field(result);
   for(j= 0; j < field->max_length+2; j++)
   fputc('-', stdout);
   fputc('+', stdout);
 }
 fputc('\n', stdout);
}


/* Print resultset metadata information */

static void my_print_result_metadata(MYSQL_RES *result)
{
 MYSQL_FIELD  *field;
 unsigned int i;
 size_t j;
 unsigned int field_count;

 mysql_field_seek(result, 0);
 if (!opt_silent)
 {
   fputc('\n', stdout);
   fputc('\n', stdout);
 }

 field_count= mysql_num_fields(result);
 for(i= 0; i< field_count; i++)
 {
   field= mysql_fetch_field(result);
   j= strlen(field->name);
   if (j < field->max_length)
   j= field->max_length;
   if (j < 4 && !IS_NOT_NULL(field->flags))
   j= 4;
   field->max_length= (unsigned long)j;
 }
 if (!opt_silent)
 {
   my_print_dashes(result);
   fputc('\t', stdout);
   fputc('|', stdout);
 }

 mysql_field_seek(result, 0);
 for(i= 0; i< field_count; i++)
 {
   field= mysql_fetch_field(result);
   if (!opt_silent)
   fprintf(stdout, " %-*s |", (int) field->max_length, field->name);
 }
 if (!opt_silent)
 {
   fputc('\n', stdout);
   my_print_dashes(result);
 }
}


/* Process the result set */

static int my_process_result_set(MYSQL_RES *result)
{
 MYSQL_ROW    row;
 MYSQL_FIELD  *field;
 unsigned int i;
 unsigned int row_count= 0;

 if (!result)
 return 0;

 my_print_result_metadata(result);

 while ((row= mysql_fetch_row(result)) != NULL)
 {
   mysql_field_seek(result, 0);
   if (!opt_silent)
   {
     fputc('\t', stdout);
     fputc('|', stdout);
   }

   for(i= 0; i< mysql_num_fields(result); i++)
   {
     field= mysql_fetch_field(result);
     if (!opt_silent)
     {
       if (row[i] == NULL)
       fprintf(stdout, " %-*s |", (int) field->max_length, "NULL");
       else if (IS_NUM(field->type))
       fprintf(stdout, " %*s |", (int) field->max_length, row[i]);
       else
       fprintf(stdout, " %-*s |", (int) field->max_length, row[i]);
     }
   }
   if (!opt_silent)
   {
     fputc('\t', stdout);
     fputc('\n', stdout);
   }
   row_count++;
 }
 if (!opt_silent)
 {
   if (row_count)
   my_print_dashes(result);

   if (mysql_errno(mysql) != 0)
   fprintf(stderr, "\n\tmysql_fetch_row() failed\n");
   else
   fprintf(stdout, "\n\t%d %s returned\n", row_count,
   row_count == 1 ? "row" : "rows");
 }
 return row_count;
}


static int my_process_result(MYSQL *mysql_arg)
{
 MYSQL_RES *result;
 int       row_count;

 if (!(result= mysql_store_result(mysql_arg)))
 return 0;

 row_count= my_process_result_set(result);

 mysql_free_result(result);
 return row_count;
}


/* Process the statement result set */

#define MAX_RES_FIELDS 50
#define MAX_FIELD_DATA_SIZE 255

static int my_process_stmt_result(MYSQL_STMT *stmt)
{
 int         field_count;
 int         row_count= 0;
 MYSQL_BIND  buffer[MAX_RES_FIELDS];
 MYSQL_FIELD *field;
 MYSQL_RES   *result;
 char        data[MAX_RES_FIELDS][MAX_FIELD_DATA_SIZE];
 ulong       length[MAX_RES_FIELDS];
 my_bool     is_null[MAX_RES_FIELDS];
 int         rc, i;

 if (!(result= mysql_stmt_result_metadata(stmt))) /* No meta info */
 {
   while (!mysql_stmt_fetch(stmt))
   row_count++;
   return row_count;
 }

 field_count= MY_MIN(mysql_num_fields(result), MAX_RES_FIELDS);

 memset(buffer, 0, sizeof(buffer));
 memset(length, 0, sizeof(length));
 memset(is_null, 0, sizeof(is_null));

 for(i= 0; i < field_count; i++)
 {
   buffer[i].buffer_type= MYSQL_TYPE_STRING;
   buffer[i].buffer_length= MAX_FIELD_DATA_SIZE;
   buffer[i].length= &length[i];
   buffer[i].buffer= (void *) data[i];
   buffer[i].is_null= &is_null[i];
 }

 rc= mysql_stmt_bind_result(stmt, buffer);
 check_execute(stmt, rc);

 rc= 1;
 mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*)&rc);
 rc= mysql_stmt_store_result(stmt);
 check_execute(stmt, rc);
 my_print_result_metadata(result);

 mysql_field_seek(result, 0);
 while ((rc= mysql_stmt_fetch(stmt)) == 0)
 {
   if (!opt_silent)
   {
     fputc('\t', stdout);
     fputc('|', stdout);
   }
   mysql_field_seek(result, 0);
   for (i= 0; i < field_count; i++)
   {
     field= mysql_fetch_field(result);
     if (!opt_silent)
     {
       if (is_null[i])
       fprintf(stdout, " %-*s |", (int) field->max_length, "NULL");
       else if (length[i] == 0)
       {
	 data[i][0]= '\0';  /* unmodified buffer */
	 fprintf(stdout, " %*s |", (int) field->max_length, data[i]);
       }
       else if (IS_NUM(field->type))
       fprintf(stdout, " %*s |", (int) field->max_length, data[i]);
       else
       fprintf(stdout, " %-*s |", (int) field->max_length, data[i]);
     }
   }
   if (!opt_silent)
   {
     fputc('\t', stdout);
     fputc('\n', stdout);
   }
   row_count++;
 }
 DIE_UNLESS(rc == MYSQL_NO_DATA);
 if (!opt_silent)
 {
   if (row_count)
   my_print_dashes(result);
   fprintf(stdout, "\n\t%d %s returned\n", row_count,
   row_count == 1 ? "row" : "rows");
 }
 mysql_free_result(result);
 return row_count;
}


/* Prepare statement, execute, and process result set for given query */

int my_stmt_result(const char *buff)
{
 MYSQL_STMT *stmt;
 int        row_count;
 int        rc;

 if (!opt_silent)
 fprintf(stdout, "\n\n %s", buff);
 stmt= mysql_simple_prepare(mysql, buff);
 check_stmt(stmt);

 rc= mysql_stmt_execute(stmt);
 check_execute(stmt, rc);

 row_count= my_process_stmt_result(stmt);
 mysql_stmt_close(stmt);

 return row_count;
}

/* Print the total number of warnings and the warnings themselves.  */

void my_process_warnings(MYSQL *conn, unsigned expected_warning_count)
{
 MYSQL_RES *result;
 int rc;

 if (!opt_silent)
 fprintf(stdout, "\n total warnings: %u (expected: %u)\n",
 mysql_warning_count(conn), expected_warning_count);

 DIE_UNLESS(mysql_warning_count(mysql) == expected_warning_count);

 rc= mysql_query(conn, "SHOW WARNINGS");
 DIE_UNLESS(rc == 0);

 result= mysql_store_result(conn);
 mytest(result);

 rc= my_process_result_set(result);
 mysql_free_result(result);
}


/* Utility function to verify a particular column data */

static void verify_col_data(const char *table, const char *col,
const char *exp_data)
{
 static char query[MAX_TEST_QUERY_LENGTH];
 MYSQL_RES *result;
 MYSQL_ROW row;
 int       rc, field= 1;

 if (table && col)
 {
   strxmov(query, "SELECT ", col, " FROM ", table, " LIMIT 1", NullS);
   if (!opt_silent)
   fprintf(stdout, "\n %s", query);
   rc= mysql_query(mysql, query);
   myquery(rc);

   field= 0;
 }

 result= mysql_use_result(mysql);
 mytest(result);

 if (!(row= mysql_fetch_row(result)) || !row[field])
 {
   fprintf(stdout, "\n *** ERROR: FAILED TO GET THE RESULT ***");
   exit(1);
 }
 if (strcmp(row[field], exp_data))
 {
   fprintf(stdout, "\n obtained: `%s` (expected: `%s`)",
   row[field], exp_data);
   DIE_UNLESS(FALSE);
 }
 mysql_free_result(result);
}


/* Utility function to verify the field members */

#define verify_prepare_field(result,no,name,org_name,type,table,	\
org_table,db,length,def)						\
do_verify_prepare_field((result),(no),(name),(org_name),(type),		\
(table),(org_table),(db),(length),(def),				\
__FILE__, __LINE__)

static void do_verify_prepare_field(MYSQL_RES *result,
unsigned int no, const char *name,
const char *org_name,
enum enum_field_types type,
const char *table,
const char *org_table, const char *db,
unsigned long length, const char *def,
const char *file, int line)
{
 MYSQL_FIELD *field;
 CHARSET_INFO *cs;
 ulonglong expected_field_length;

 if (!(field= mysql_fetch_field_direct(result, no)))
 {
   fprintf(stdout, "\n *** ERROR: FAILED TO GET THE RESULT ***");
   exit(1);
 }
 cs= get_charset(field->charsetnr, 0);
 DIE_UNLESS(cs);
 if ((expected_field_length= length * cs->mbmaxlen) > UINT_MAX32)
 expected_field_length= UINT_MAX32;
 if (!opt_silent)
 {
   fprintf(stdout, "\n field[%d]:", no);
   fprintf(stdout, "\n    name     :`%s`\t(expected: `%s`)", field->name, name);
   fprintf(stdout, "\n    org_name :`%s`\t(expected: `%s`)",
   field->org_name, org_name);
   fprintf(stdout, "\n    type     :`%d`\t(expected: `%d`)", field->type, type);
   if (table)
   fprintf(stdout, "\n    table    :`%s`\t(expected: `%s`)",
   field->table, table);
   if (org_table)
   fprintf(stdout, "\n    org_table:`%s`\t(expected: `%s`)",
   field->org_table, org_table);
   fprintf(stdout, "\n    database :`%s`\t(expected: `%s`)", field->db, db);
   fprintf(stdout, "\n    length   :`%lu`\t(expected: `%llu`)",
   field->length, expected_field_length);
   fprintf(stdout, "\n    maxlength:`%ld`", field->max_length);
   fprintf(stdout, "\n    charsetnr:`%d`", field->charsetnr);
   fprintf(stdout, "\n    default  :`%s`\t(expected: `%s`)",
   field->def ? field->def : "(null)", def ? def: "(null)");
   fprintf(stdout, "\n");
 }
 DIE_UNLESS(strcmp(field->name, name) == 0);
 DIE_UNLESS(strcmp(field->org_name, org_name) == 0);
 /*
 XXX: silent column specification change works based on number of
 bytes a column occupies. So CHAR -> VARCHAR upgrade is possible even
 for CHAR(2) column if its character set is multibyte.
 VARCHAR -> CHAR downgrade won't work for VARCHAR(3) as one would
 expect.
 */
 if (cs->mbmaxlen == 1)
 {
   if (field->type != type)
   {
     fprintf(stderr,
     "Expected field type: %d,  got type: %d in file %s, line %d\n",
     (int) type, (int) field->type, file, line);
     DIE_UNLESS(field->type == type);
   }
 }
 if (table)
 DIE_UNLESS(strcmp(field->table, table) == 0);
 if (org_table)
 DIE_UNLESS(strcmp(field->org_table, org_table) == 0);
 DIE_UNLESS(strcmp(field->db, db) == 0);
 /*
 Character set should be taken into account for multibyte encodings, such
 as utf8. Field length is calculated as number of characters * maximum
 number of bytes a character can occupy.
 */
 if (length && (field->length != expected_field_length))
 {
   fprintf(stderr, "Expected field length: %llu,  got length: %lu\n",
   expected_field_length, field->length);
   DIE_UNLESS(field->length == expected_field_length);
 }
 if (def)
 DIE_UNLESS(strcmp(field->def, def) == 0);
}


/* Utility function to verify the parameter count */

static void verify_param_count(MYSQL_STMT *stmt, long exp_count)
{
 long param_count= mysql_stmt_param_count(stmt);
 if (!opt_silent)
 fprintf(stdout, "\n total parameters in stmt: `%ld` (expected: `%ld`)",
 param_count, exp_count);
 DIE_UNLESS(param_count == exp_count);
}


/* Utility function to verify the total affected rows */

static void verify_st_affected_rows(MYSQL_STMT *stmt, ulonglong exp_count)
{
 ulonglong affected_rows= mysql_stmt_affected_rows(stmt);
 if (!opt_silent)
 fprintf(stdout, "\n total affected rows: `%ld` (expected: `%ld`)",
 (long) affected_rows, (long) exp_count);
 DIE_UNLESS(affected_rows == exp_count);
}


/* Utility function to verify the total affected rows */

static void verify_affected_rows(ulonglong exp_count)
{
 ulonglong affected_rows= mysql_affected_rows(mysql);
 if (!opt_silent)
 fprintf(stdout, "\n total affected rows: `%ld` (expected: `%ld`)",
 (long) affected_rows, (long) exp_count);
 DIE_UNLESS(affected_rows == exp_count);
}


/* Utility function to verify the total fields count */

static void verify_field_count(MYSQL_RES *result, uint exp_count)
{
 uint field_count= mysql_num_fields(result);
 if (!opt_silent)
 fprintf(stdout, "\n total fields in the result set: `%d` (expected: `%d`)",
 field_count, exp_count);
 DIE_UNLESS(field_count == exp_count);
}


/* Utility function to execute a query using prepare-execute */

#ifndef EMBEDDED_LIBRARY
static void execute_prepare_query(const char *query, ulonglong exp_count)
{
 MYSQL_STMT *stmt;
 ulonglong  affected_rows;
 int        rc;

 stmt= mysql_simple_prepare(mysql, query);
 check_stmt(stmt);

 rc= mysql_stmt_execute(stmt);
 myquery(rc);

 affected_rows= mysql_stmt_affected_rows(stmt);
 if (!opt_silent)
 fprintf(stdout, "\n total affected rows: `%ld` (expected: `%ld`)",
 (long) affected_rows, (long) exp_count);

 DIE_UNLESS(affected_rows == exp_count);
 mysql_stmt_close(stmt);
}
#endif

/*
Accepts arbitrary number of queries and runs them against the database.
Used to fill tables for each test.
*/

void fill_tables(const char **query_list, unsigned query_count)
{
 int rc;
 const char **query;
 DBUG_ENTER("fill_tables");
 for (query= query_list; query < query_list + query_count;
      ++query)
 {
   rc= mysql_query(mysql, *query);
   myquery(rc);
 }
 DBUG_VOID_RETURN;
}

/*
All state of fetch from one statement: statement handle, out buffers,
fetch position.
See fetch_n for for the only use case.
*/

enum { MAX_COLUMN_LENGTH= 255 };

typedef struct st_stmt_fetch
{
const char *query;
unsigned stmt_no;
MYSQL_STMT *handle;
my_bool is_open;
MYSQL_BIND *bind_array;
char **out_data;
unsigned long *out_data_length;
unsigned column_count;
unsigned row_count;
} Stmt_fetch;


/*
Create statement handle, prepare it with statement, execute and allocate
fetch buffers.
*/

void stmt_fetch_init(Stmt_fetch *fetch, unsigned stmt_no_arg,
const char *query_arg)
{
 unsigned long type= CURSOR_TYPE_READ_ONLY;
 int rc;
 unsigned i;
 MYSQL_RES *metadata;
 DBUG_ENTER("stmt_fetch_init");

 /* Save query and statement number for error messages */
 fetch->stmt_no= stmt_no_arg;
 fetch->query= query_arg;

 fetch->handle= mysql_stmt_init(mysql);

 rc= mysql_stmt_prepare(fetch->handle, fetch->query, (ulong)strlen(fetch->query));
 check_execute(fetch->handle, rc);

 /*
 The attribute is sent to server on execute and asks to open read-only
 for result set
 */
 mysql_stmt_attr_set(fetch->handle, STMT_ATTR_CURSOR_TYPE,
 (const void*) &type);

 rc= mysql_stmt_execute(fetch->handle);
 check_execute(fetch->handle, rc);

 /* Find out total number of columns in result set */
 metadata= mysql_stmt_result_metadata(fetch->handle);
 fetch->column_count= mysql_num_fields(metadata);
 mysql_free_result(metadata);

 /*
 Now allocate bind handles and buffers for output data:
 calloc memory to reduce number of MYSQL_BIND members we need to
 set up.
 */

 fetch->bind_array= (MYSQL_BIND *) calloc(1, sizeof(MYSQL_BIND) *
 fetch->column_count);
 fetch->out_data= (char**) calloc(1, sizeof(char*) * fetch->column_count);
 fetch->out_data_length= (ulong*) calloc(1, sizeof(ulong) *
                                         fetch->column_count);
 for (i= 0; i < fetch->column_count; ++i)
 {
   fetch->out_data[i]= (char*) calloc(1, MAX_COLUMN_LENGTH);
   fetch->bind_array[i].buffer_type= MYSQL_TYPE_STRING;
   fetch->bind_array[i].buffer= fetch->out_data[i];
   fetch->bind_array[i].buffer_length= MAX_COLUMN_LENGTH;
   fetch->bind_array[i].length= fetch->out_data_length + i;
 }

 mysql_stmt_bind_result(fetch->handle, fetch->bind_array);

 fetch->row_count= 0;
 fetch->is_open= TRUE;

 /* Ready for reading rows */
 DBUG_VOID_RETURN;
}


/* Fetch and print one row from cursor */

int stmt_fetch_fetch_row(Stmt_fetch *fetch)
{
 int rc;
 unsigned i;
 DBUG_ENTER("stmt_fetch_fetch_row");

 if ((rc= mysql_stmt_fetch(fetch->handle)) == 0)
 {
   ++fetch->row_count;
   if (!opt_silent)
   printf("Stmt %d fetched row %d:\n", fetch->stmt_no, fetch->row_count);
   for (i= 0; i < fetch->column_count; ++i)
   {
     fetch->out_data[i][fetch->out_data_length[i]]= '\0';
     if (!opt_silent)
     printf("column %d: %s\n", i+1, fetch->out_data[i]);
   }
 }
 else
 fetch->is_open= FALSE;
 DBUG_RETURN(rc);
}


void stmt_fetch_close(Stmt_fetch *fetch)
{
 unsigned i;
 DBUG_ENTER("stmt_fetch_close");

 for (i= 0; i < fetch->column_count; ++i)
 free(fetch->out_data[i]);
 free(fetch->out_data);
 free(fetch->out_data_length);
 free(fetch->bind_array);
 mysql_stmt_close(fetch->handle);
 DBUG_VOID_RETURN;
}

/*
For given array of queries, open query_count cursors and fetch
from them in simultaneous manner.
In case there was an error in one of the cursors, continue
reading from the rest.
*/

enum fetch_type { USE_ROW_BY_ROW_FETCH= 0, USE_STORE_RESULT= 1 };

my_bool fetch_n(const char **query_list, unsigned query_count,
enum fetch_type fetch_type)
{
 unsigned open_statements= query_count;
 int rc, error_count= 0;
 Stmt_fetch *fetch_array= (Stmt_fetch*) calloc(1, sizeof(Stmt_fetch) *
 query_count);
 Stmt_fetch *fetch;
 DBUG_ENTER("fetch_n");

 for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
 {
   /* Init will exit(1) in case of error */
   stmt_fetch_init(fetch, fetch - fetch_array,
   query_list[fetch - fetch_array]);
 }

 if (fetch_type == USE_STORE_RESULT)
 {
   for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
   {
     rc= mysql_stmt_store_result(fetch->handle);
     check_execute(fetch->handle, rc);
   }
 }

 while (open_statements)
 {
   for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
   {
     if (fetch->is_open && (rc= stmt_fetch_fetch_row(fetch)))
     {
       open_statements--;
       /*
       We try to fetch from the rest of the statements in case of
       error
       */
       if (rc != MYSQL_NO_DATA)
       {
	 fprintf(stderr,
	 "Got error reading rows from statement %d,\n"
	 "query is: %s,\n"
	 "error message: %s", (int) (fetch - fetch_array),
	 fetch->query,
	 mysql_stmt_error(fetch->handle));
	 error_count++;
       }
     }
   }
 }
 if (error_count)
 fprintf(stderr, "Fetch FAILED");
 else
 {
   unsigned total_row_count= 0;
   for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
   total_row_count+= fetch->row_count;
   if (!opt_silent)
   printf("Success, total rows fetched: %d\n", total_row_count);
 }
 for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch)
 stmt_fetch_close(fetch);
 free(fetch_array);
 DBUG_RETURN(error_count != 0);
}

/* Separate thread query to test some cases */

static my_bool thread_query(const char *query)
{
 MYSQL *l_mysql;
 my_bool error;

 error= 0;
 if (!opt_silent)
 fprintf(stdout, "\n in thread_query(%s)", query);
 if (!(l_mysql= mysql_client_init(NULL)))
 {
   myerror("mysql_client_init() failed");
   return 1;
 }
 if (!(mysql_real_connect(l_mysql, opt_host, opt_user,
 opt_password, current_db, opt_port,
 opt_unix_socket, 0)))
 {
   myerror("connection failed");
   error= 1;
   goto end;
 }
 l_mysql->reconnect= 1;
 if (mysql_query(l_mysql, query))
 {
   fprintf(stderr, "Query failed (%s)\n", mysql_error(l_mysql));
   error= 1;
   goto end;
 }
 mysql_commit(l_mysql);
 end:
 mysql_close(l_mysql);
 return error;
}


static struct my_option client_test_long_options[] =
{
{"basedir", 'b', "Basedir for tests.", &opt_basedir,
 &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"count", 't', "Number of times test to be executed", &opt_count_read,
 &opt_count_read, 0, GET_UINT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0},
{"database", 'D', "Database to use", &opt_db, &opt_db,
 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"do-not-drop-database", 'd', "Do not drop database while disconnecting",
  0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
{"debug", '#', "Output debug log", &default_dbug_option,
 &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
{"help", '?', "Display this help and exit", 0, 0, 0, GET_NO_ARG, NO_ARG, 0,
 0, 0, 0, 0, 0},
{"host", 'h', "Connect to host", &opt_host, &opt_host,
 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"password", 'p',
 "Password to use when connecting to server. If password is not given it's asked from the tty.",
 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
{"port", 'P', "Port number to use for connection or 0 for default to, in "
 "order of preference, my.cnf, $MYSQL_TCP_PORT, "
 #if MYSQL_PORT_DEFAULT == 0
 "/etc/services, "
 #endif
 "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
 &opt_port, &opt_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"server-arg", 'A', "Send embedded server this as a parameter.",
 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"show-tests", 'T', "Show all tests' names", 0, 0, 0, GET_NO_ARG, NO_ARG,
 0, 0, 0, 0, 0, 0},
{"silent", 's', "Be more silent", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0,
 0},
#if defined (_WIN32) && !defined (EMBEDDED_LIBRARY)
{"shared-memory-base-name", 'm', "Base name of shared memory.",
 &shared_memory_base_name, (uchar**)&shared_memory_base_name, 0,
 GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
#endif
{"socket", 'S', "Socket file to use for connection",
 &opt_unix_socket, &opt_unix_socket, 0, GET_STR,
 REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"testcase", 'c',
 "May disable some code when runs as mysql-test-run testcase.",
 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0},
{"user", 'u', "User for login if not current user", &opt_user,
 &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"vardir", 'v', "Data dir for tests.", &opt_vardir,
 &opt_vardir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"getopt-ll-test", 'g', "Option for testing bug in getopt library",
 &opt_getopt_ll_test, &opt_getopt_ll_test, 0,
 GET_LL, REQUIRED_ARG, 0, 0, LLONG_MAX, 0, 0, 0},
{"plugin_dir", 0, "Directory for client-side plugins.",
 &opt_plugin_dir, &opt_plugin_dir, 0,
 GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"default_auth", 0, "Default authentication client-side plugin to use.",
 &opt_default_auth, &opt_default_auth, 0,
 GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
{"secure-auth", 0, "Refuse client connecting to server if it"
  " uses old (pre-4.1.1) protocol.", &opt_secure_auth,
  &opt_secure_auth, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0},
{ 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
};


static void usage(void)
{
/* show the usage string when the user asks for this */
 putc('\n', stdout);
 printf("%s  Ver %s Distrib %s, for %s (%s)\n",
 my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE);
 puts("By Monty, Venu, Kent and others\n");
 printf("\
Copyright (C) 2002-2004 MySQL AB\n\
This software comes with ABSOLUTELY NO WARRANTY. This is free software,\n\
and you are welcome to modify and redistribute it under the GPL license\n");
 printf("Usage: %s [OPTIONS] [TESTNAME1 TESTNAME2...]\n", my_progname);
 my_print_help(client_test_long_options);
 print_defaults("my", client_test_load_default_groups);
 my_print_variables(client_test_long_options);
}


static struct my_tests_st *get_my_tests();  /* To be defined in main .c file */

static struct my_tests_st *my_testlist= 0;

static my_bool
get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)),
char *argument)
{
 switch (optid) {
 case '#':
 DBUG_PUSH(argument ? argument : default_dbug_option);
 break;
 case 'c':
 opt_testcase = 1;
 break;
 case 'p':
 if (argument)
 {
   char *start=argument;
   my_free(opt_password);
   opt_password= my_strdup(PSI_NOT_INSTRUMENTED,
                           argument, MYF(MY_FAE));
   while (*argument) *argument++= 'x';               /* Destroy argument */
   if (*start)
   start[1]=0;
 }
 else
 tty_password= 1;
 break;
 case 's':
 if (argument == disabled_my_option)
 opt_silent= 0;
 else
 opt_silent++;
 break;
 case 'd':
 opt_drop_db= 0;
 break;
 case 'A':
 /*
 When the embedded server is being tested, the test suite needs to be
 able to pass command-line arguments to the embedded server so it can
 locate the language files and data directory. The test suite
 (mysql-test-run) never uses config files, just command-line options.
 */
 if (!embedded_server_arg_count)
 {
   embedded_server_arg_count= 1;
   embedded_server_args[0]= (char*) "";
 }
 if (embedded_server_arg_count == MAX_SERVER_ARGS-1 ||
 !(embedded_server_args[embedded_server_arg_count++]=
 my_strdup(PSI_NOT_INSTRUMENTED,
           argument, MYF(MY_FAE))))
 {
   DIE("Can't use server argument");
 }
 break;
 case 'T':
 {
   struct my_tests_st *fptr;

   printf("All possible test names:\n\n");
   for (fptr= my_testlist; fptr->name; fptr++)
   printf("%s\n", fptr->name);
   exit(0);
   break;
 }
 case '?':
 case 'I':                                     /* Info */
 usage();
 exit(0);
 break;
 }
 return 0;
}

static void get_options(int *argc, char ***argv)
{
 int ho_error;

 /* Copy argv from load_defaults, so we can free it when done. */
 defaults_argv= *argv;
 /* reset --silent option */
 opt_silent= 0;

 if ((ho_error= handle_options(argc, argv, client_test_long_options,
 get_one_option)))
 exit(ho_error);

 if (tty_password)
 opt_password= get_tty_password(NullS);
 return;
}

/*
Print the test output on successful execution before exiting
*/

static void print_test_output()
{
 if (opt_silent < 3)
 {
   fprintf(stdout, "\n\n");
   fprintf(stdout, "All '%d' tests were successful (in '%d' iterations)",
   test_count-1, opt_count);
   if (!opt_silent)
   {
     fprintf(stdout, "\n  Total execution time: %g SECS", total_time);
     if (opt_count > 1)
     fprintf(stdout, " (Avg: %g SECS)", total_time/opt_count);
   }

   fprintf(stdout, "\n\n!!! SUCCESS !!!\n");
 }
}

/***************************************************************************
main routine
***************************************************************************/


int main(int argc, char **argv)
{
 int i, rc;
 int version;
 char **tests_to_run= NULL, **curr_test;
 struct my_tests_st *fptr;
 my_testlist= get_my_tests();

 MY_INIT(argv[0]);

 /* Copy the original arguments, so it can be reused for restarting. */
 original_argc= argc;
 original_argv= (char**)malloc(argc * sizeof(char*));
 if (argc && !original_argv)
 exit(1);
 for (i= 0; i < argc; i++)
 original_argv[i]= strdup(argv[i]);

 if (load_defaults("my", client_test_load_default_groups, &argc, &argv))
 exit(1);

 get_options(&argc, &argv);

 /* Set main opt_count. */
 opt_count= opt_count_read;

 /* If there are any arguments left (named tests), save them. */
 if (argc)
 {
   tests_to_run= (char**)malloc((argc + 1) * sizeof(char*));
   if (!tests_to_run)
   exit(1);
   for (i= 0; i < argc; i++)
   tests_to_run[i]= strdup(argv[i]);
   tests_to_run[i]= NULL;
 }

 if (mysql_server_init(embedded_server_arg_count,
 embedded_server_args,
 (char**) embedded_server_groups))
 DIE("Can't initialize MySQL server");

 /* connect to server with no flags, default protocol, auto reconnect true */
 mysql= client_connect(0, MYSQL_PROTOCOL_DEFAULT, 1);
 version = mysql_get_server_version(mysql);

 printf("\nRunning tests against server version: %d\n", version);

 total_time= 0;
 for (iter_count= 1; iter_count <= opt_count; iter_count++)
 {
   /* Start of tests */
   test_count= 1;
   start_time= time((time_t *)0);
   if (!tests_to_run)
   {
     for (fptr = my_testlist; fptr->name; fptr++)
     {
       if (fptr->version_min != 0 && version < fptr->version_min)
       {
          printf(
            "\n#### Skipped test %s "
            "because it requires server version %d at least\n",
            fptr->name, fptr->version_min
          );
          continue;
       }

       if (fptr->version_max != 0 && version > fptr->version_max)
       {
          printf(
            "\n#### Skipped test %s "
            "because it requires server version %d at most\n",
            fptr->name, fptr->version_max
          );
          continue;
       }

       rc = mysql_query(mysql, "use client_test_db");
       myquery(rc);
       (*fptr->function)();
     }
   }
   else
   {
     for (curr_test= tests_to_run ; *curr_test ; curr_test++)
     {
       for (fptr= my_testlist; fptr->name; fptr++)
       {
	 if (!strcmp(fptr->name, *curr_test))
	 {
       int rc = mysql_query(mysql, "use client_test_db");
       myquery(rc);
	   (*fptr->function)();
	   break;
	 }
       }
       if (!fptr->name)
       {
	 fprintf(stderr, "\n\nGiven test not found: '%s'\n", *argv);
	 fprintf(stderr, "See legal test names with %s -T\n\nAborting!\n",
	 my_progname);
	 client_disconnect(mysql);
	 free_defaults(defaults_argv);
	 mysql_server_end();
	 exit(1);
       }
     }
   }

   end_time= time((time_t *)0);
   total_time+= difftime(end_time, start_time);

   /* End of tests */
 }

 client_disconnect(mysql);    /* disconnect from server */

 free_defaults(defaults_argv);
 print_test_output();

 while (embedded_server_arg_count > 1)
 my_free(embedded_server_args[--embedded_server_arg_count]);

 mysql_server_end();

 my_end(0);

 for (i= 0; i < original_argc; i++)
 free(original_argv[i]);
 if (original_argc)
 free(original_argv);
 if (tests_to_run)
 {
   for (curr_test= tests_to_run ; *curr_test ; curr_test++)
   free(*curr_test);
   free(tests_to_run);
 }
 my_free(opt_password);
 my_free(opt_host);
 exit(0);
}
